package com.kylin.common.security.filter;

import com.alibaba.fastjson2.JSON;
import com.kylin.common.core.beans.GlobalResultCode;
import com.kylin.common.core.beans.ResultBean;
import com.kylin.common.core.util.SpringUtils;
import com.kylin.common.redis.service.RedisService;
import com.kylin.common.security.config.LoginProperties;
import com.kylin.common.security.config.SecurityProperties;
import com.kylin.common.security.domain.ClaimsInfo;
import com.kylin.common.security.domain.LoginUser;
import com.kylin.common.security.exception.AuthErrorMessage;
import com.kylin.common.security.exception.CaptchaException;
import com.kylin.common.security.exception.CustomAuthenticationException;
import com.kylin.common.security.service.CaptchaService;
import com.kylin.common.security.service.handler.LoginHandler;
import com.kylin.common.security.service.handler.LoginHandlerFactory;
import com.kylin.common.security.token.CustomAuthenticationToken;
import com.kylin.common.security.util.JwtUtils;
import com.kylin.common.security.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.bind.ServletRequestUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Objects;

/**
 * 自定义认证过滤器
 * @author wuhao
 * @version 1.0 - 2022/10/10
 */
@Slf4j
public class JwtLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private CaptchaService captchaService;
    private SecurityProperties securityProperties;
    private LoginHandlerFactory loginHandlerFactory;

    public JwtLoginAuthenticationFilter(CaptchaService captchaService, SecurityProperties securityProperties, LoginHandlerFactory loginHandlerFactory) {
        super(new AntPathRequestMatcher("/auth/token", "POST"));
        this.captchaService = captchaService;
        this.securityProperties = securityProperties;
        this.loginHandlerFactory = loginHandlerFactory;
    }

    /**
     * 认证处理
     * @param request 请求
     * @param response 响应
     * @return  
     * @author wuhao
     * @since 2.6.0 - 2022/10/10
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

        // 1.是否开启验证码校验功能
        LoginProperties loginProperties = getLoginProperties(request);
        if(loginProperties.isEnableCaptcha()) {
            // 获取检验参数
            String captcha = ServletRequestUtils.getStringParameter(request, "captcha");
            String uuid = ServletRequestUtils.getStringParameter(request, "uuid");
            captchaService.checkCaptcha(captcha, uuid);
        }

        // 2. 封装成 Token 调用 AuthenticationManager 的 authenticated 方法，该方法中根据 Token 的类型去调用对应 Provider 的 authenticated
        CustomAuthenticationToken token = new CustomAuthenticationToken("");
        this.setDetails(request, token);

        // 3. 返回 authenticated 方法的返回值
        return this.getAuthenticationManager().authenticate(token);
    }

    /**
     * 登录成功
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) {
        // 根据授权模式获取过期时间等
        LoginProperties loginProperties = getLoginProperties(request);

        LoginUser user = (LoginUser) auth.getPrincipal();
        ClaimsInfo claims = new ClaimsInfo(user.getUserId(), loginProperties.getLoginType());

        // 生成token
        String token = JwtUtils.createJwt(JSON.toJSONString(claims), loginProperties.getExpireTimes());
        String refreshToken = JwtUtils.createJwt(user.getUserId() + "", loginProperties.getRefreshTokenTimes());
        HashMap<String, Object> map = new HashMap<>(4);
        map.put("access_token",token);
        map.put("expires_time", loginProperties.getExpireTimes());
        map.put("refresh_token",refreshToken);
        map.put("refresh_expires_time", loginProperties.getRefreshTokenTimes());

        // 将用户信息放入缓存，以便在权限认证的时候可以获取权限信息
        RedisService redisService = SpringUtils.getBean(RedisService.class);
        redisService.setCacheObject(claims.getTokenKey(), user);

        ResponseUtils.out(response, ResultBean.onSuccess(map));
    }

    /**
     * 登录失败
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        log.error(e.getMessage());
        // 处理登录过程中抛出的异常，loadUserByUsername中，这里不能直接外网抛出，全局异常处理器是捕获不到的，参考：https://blog.csdn.net/weixin_45630446/article/details/120337938
        int errorCode = GlobalResultCode.UNAUTHORIZED.getCode();
        String errorMsg = "";
        if(e instanceof LockedException) {
            errorMsg =  AuthErrorMessage.ACCOUNT_LOCKED.getMessage();
        }else if(e instanceof CaptchaException) {
            // 验证码校验失败
            errorMsg = e.getMessage();
        } else if( e instanceof UsernameNotFoundException || e instanceof  BadCredentialsException) {
            // 账号不存在/密码错误
            errorMsg = AuthErrorMessage.ACCOUNT_PASSWORD_ERROR.getMessage();
        } else if (e instanceof InternalAuthenticationServiceException) {
            errorMsg = e.getMessage();
        } else if(e instanceof CustomAuthenticationException) {
            errorCode = ((CustomAuthenticationException) e).getCode();
            errorMsg = e.getMessage();
        } else  {
            errorMsg = e.getMessage();
        }
        //...具体错误细节处理

        ResultBean<Serializable> resultBean = new ResultBean<>(errorCode, errorMsg);
        ResponseUtils.out(response, resultBean);
    }


    protected void setDetails(HttpServletRequest request, CustomAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    /** 获取登录配置 */
    private LoginProperties getLoginProperties(HttpServletRequest request) {
        // 先根据个性化配置来，如果个性化配置没有则获取全局的配置
        String grantType = request.getParameter("grant_type");
        LoginHandler loginHandler = loginHandlerFactory.getLoginHandler(grantType);
        LoginProperties loginProperties = loginHandler.getLoginProperties();
        if(Objects.isNull(loginProperties.isEnableCaptcha())) {
            loginProperties.setEnableCaptcha(securityProperties.getLogin().isEnableCaptcha());
        }
        if(StringUtils.isBlank(loginProperties.getLoginType())) {
            loginProperties.setLoginType(securityProperties.getLogin().getLoginType());
        }
        if(Objects.isNull(loginProperties.getExpireTimes())) {
            loginProperties.setExpireTimes(securityProperties.getLogin().getExpireTimes());
        }
        if(Objects.isNull(loginProperties.getRefreshTokenTimes())) {
            loginProperties.setRefreshTokenTimes(securityProperties.getLogin().getRefreshTokenTimes());
        }
        return loginProperties;
    }
}
